package net.maxupeng.asm74;

import net.maxupeng.asm74.antlr.ASM74Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import static net.maxupeng.asm74.util.NumberUtil.str2int;

public class Compiler {

    private static final Map<String, Integer> globalLabelPageMap = new HashMap<>();
    private static final Map<String, Integer> globalLabelAddrMap = new HashMap<>();

    static {
        globalLabelAddrMap.put("A", 0x8000);
        globalLabelAddrMap.put("B", 0x8004);
        globalLabelAddrMap.put("SP", 0x8008);
        globalLabelAddrMap.put("TMP0", 0x8010);
        globalLabelAddrMap.put("TMP1", 0x8020);
        globalLabelAddrMap.put("TMP2", 0x8030);
        globalLabelAddrMap.put("TMP3", 0x8040);
        globalLabelAddrMap.put("TMP4", 0x8050);
        globalLabelAddrMap.put("STACK_BASE", 0x9FFF);
        globalLabelAddrMap.put("PAGE", 0xFFFF);
    }

    private static final Map<ASM74Parser.ProgContext, Map<String, Integer>> localLabelPageMap = new HashMap<>();
    private static final Map<ASM74Parser.ProgContext, Map<String, Integer>> localLabelAddrMap = new HashMap<>();


    private static final Pattern localLabelRegex = Pattern.compile("L[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]");

    public static void setLabelPageAddr(ParserRuleContext ruleCtx, String name, int page, int addr) {
        if (isLocalLabel(name)) {
            if (ruleCtx == null) {
                System.out.println("Fuck");
                System.exit(1);
            }

            ParserRuleContext progCtx = ruleCtx;
            while (progCtx.getParent() != null) {
                progCtx = progCtx.getParent();
            }

            if (!(progCtx instanceof ASM74Parser.ProgContext)) {
                System.out.println("Fuck");
                System.exit(1);
            }

            localLabelPageMap.computeIfAbsent((ASM74Parser.ProgContext) progCtx, k -> new HashMap<>()).put(name, page);
            localLabelAddrMap.computeIfAbsent((ASM74Parser.ProgContext) progCtx, k -> new HashMap<>()).put(name, addr);
        } else {
            globalLabelPageMap.put(name, page);
            globalLabelAddrMap.put(name, addr);
        }
    }

    public static int getLabelAddr(ParserRuleContext ruleCtx, String name) {
        if (isLocalLabel(name)) {
            if (ruleCtx == null) {
                System.out.println("Fuck");
                System.exit(1);
            }

            ParserRuleContext progCtx = ruleCtx;
            while (progCtx.getParent() != null) {
                progCtx = progCtx.getParent();
            }

            if (!(progCtx instanceof ASM74Parser.ProgContext)) {
                System.out.println("Fuck");
                System.exit(1);
            }

            return localLabelAddrMap.get((ASM74Parser.ProgContext) progCtx).get(name);
        } else {
            return globalLabelAddrMap.get(name);
        }
    }

    public static boolean isLocalLabel(String s) {
        return localLabelRegex.matcher(s).matches();
    }

    public static int getLabelPage(ParserRuleContext ruleCtx, String name) {
        if (isLocalLabel(name)) {
            ParserRuleContext progCtx = ruleCtx;
            while (progCtx.getParent() != null) {
                progCtx = progCtx.getParent();
            }

            if (!(progCtx instanceof ASM74Parser.ProgContext)) {
                System.out.println("Fuck");
                System.exit(1);
            }

            return localLabelPageMap.get((ASM74Parser.ProgContext) progCtx).get(name);
        } else {
            return globalLabelPageMap.get(name);
        }
    }


    // 表达式求值相关方法
    public static int evalExpr2(ASM74Parser.Expr2Context expr2Ctx, Map<String, Integer> varMap) {
        expr2Ctx.getParent();

        if (expr2Ctx.NUMBER() != null) {
            return str2int(expr2Ctx.NUMBER().getText());
        } else if (expr2Ctx.ID() != null) {
            if (expr2Ctx.expr() != null) {
                switch (expr2Ctx.ID().getText()) {
                    case "P": {
                        String label = expr2Ctx.expr().getText();
                        return getLabelPage(expr2Ctx, label);
                    }
                    case "H": {
                        return (evalExpr(expr2Ctx.expr(), null) >> 8) & 0xff;
                    }
                    case "L": {
                        return evalExpr(expr2Ctx.expr(), null) & 0xff;
                    }
                }
            } else {
                String label = expr2Ctx.ID().getText();
                if (varMap != null) {
                    return varMap.get(label);
                } else {
                    return getLabelAddr(expr2Ctx, label);
                }
            }
        }

        System.out.println("Impossible");
        System.exit(1);
        return 0;
    }

    public static int evalExpr2(ASM74Parser.Expr2Context expr2Ctx) {
        return evalExpr2(expr2Ctx, null);
    }

    public static int evalExpr1(ASM74Parser.Expr1Context expr1Ctx, Map<String, Integer> varMap) {
        int result = 0;
        String op = "";
        for (int i = 0; i < expr1Ctx.getChildCount(); i++) {
            ParseTree child = expr1Ctx.getChild(i);
            if (child instanceof ASM74Parser.Expr2Context) {
                if (op.equals("")) {
                    result = evalExpr2((ASM74Parser.Expr2Context) child, varMap);
                } else {
                    if (op.equals("*")) {
                        result *= evalExpr2((ASM74Parser.Expr2Context) child, varMap);
                    } else if (op.equals("/")) {
                        result /= evalExpr2((ASM74Parser.Expr2Context) child, varMap);
                    }
                }
            } else {
                op = child.getText();
            }
        }

        return result;
    }

    public static int evalExpr1(ASM74Parser.Expr1Context expr1Ctx) {
        return evalExpr1(expr1Ctx, null);
    }

    public static int evalExpr(ASM74Parser.ExprContext exprCtx, Map<String, Integer> varMap) {
        int result = 0;
        String op = "";
        for (int i = 0; i < exprCtx.getChildCount(); i++) {
            ParseTree child = exprCtx.getChild(i);
            if (child instanceof ASM74Parser.Expr1Context) {
                if (op.equals("")) {
                    result = evalExpr1((ASM74Parser.Expr1Context) child, varMap);
                } else {
                    if (op.equals("+")) {
                        result += evalExpr1((ASM74Parser.Expr1Context) child, varMap);
                    } else if (op.equals("-")) {
                        result -= evalExpr1((ASM74Parser.Expr1Context) child, varMap);
                    }
                }
            } else {
                op = child.getText();
            }
        }

        return result;
    }

    public static int evalExpr(ASM74Parser.ExprContext exprCtx) {
        return evalExpr(exprCtx, null);
    }

}
